"""
Author: Morphlng
Date: 2024-01-22 16:51:18
LastEditTime: 2024-03-29 18:15:06
LastEditors: Morphlng
Description: Adapter interface for communication with simulators.
FilePath: /DrivingGym/src/driving_gym/simulation/adapter_interface.py
"""

from __future__ import annotations

from typing import Any, Callable

import numpy as np

from driving_gym.simulation.common.geom import BoundingBox, Transform, Vector3D
from driving_gym.simulation.common.hd_map import HDMap


class AdapterInterface(object):
    def __init__(self, config: dict):
        """Initializes the adapter interface.

        Args:
            config (dict): The configuration for the simulator.
        """
        self.config = config

    def initialize(self):
        """Initializes the adapter based on config."""
        raise NotImplementedError

    def cleanup(self):
        """Cleans up the adapter interface."""
        raise NotImplementedError

    def get_map(self) -> HDMap:
        """Gets the map of the simulator.

        Returns:
            HDMap: The map of the simulator.
        """
        pass

    def get_transform(self, identifier: str) -> Transform:
        """Gets the transform of an actor.

        Args:
            identifier: The identifier of the actor to get the transform of.

        Returns:
            Transform: The transform of the actor with the given identifier.

        Note:
            - Transform is a combination of Location and Rotation.
            - Some simulator may not provide Rotation information.
            - Rotation are in degrees, no specific range limitation.
        """
        pass

    def get_velocity(self, identifier: str) -> Vector3D:
        """Gets the velocity of an actor.

        Args:
            identifier: The identifier of the actor to get the velocity of.

        Returns:
            Vector3D: The velocity of the actor with the given identifier.

        Note:
            - Velocity is in m/s.
            - Use velocity.length() to get the speed.
        """
        pass

    def get_acceleration(self, identifier: str) -> Vector3D:
        """Gets the acceleration of an actor.

        Args:
            identifier: The identifier of the actor to get the acceleration of.

        Returns:
            Vector3D: The acceleration of the actor with the given identifier.

        Note:
            - Acceleration is in m/s^2.
            - Use acceleration.length() to get the magnitude of the acceleration.
        """
        pass

    def get_bounding_box(self, identifier: str) -> BoundingBox:
        """Gets the bounding box of an actor.

        Args:
            identifier: The identifier of the actor to get the bounding box of.

        Returns:
            BoundingBox: The bounding box of the actor with the given identifier.

        Note:
            - Bounding box is in local frame.
            - Use bbox.transform_to_world_frame(actor.transform) to get the world position.
        """
        pass

    def get_sensor_data(self, sensor_identifier: str) -> np.ndarray:
        """Gets the data from a sensor.

        Args:
            sensor_identifier: The identifier of the sensor to get the data from.

        Returns:
            np.ndarray: The data from the sensor with the given identifier.

        Note:
            - Sensor Identifier should be `"{parent_name}:{sensor_name}"` in config key.
        """
        pass

    def get_actors(self) -> list[str]:
        """Returns the valid identifier of all actors the adapter is aware of."""
        raise NotImplementedError

    def get_sensors(self) -> list[str]:
        """Returns the valid identifier of all sensors the adapter is aware of."""
        raise NotImplementedError

    def load_scenario(self, scenario: dict):
        """Loads a scenario into the simulator.

        Args:
            scenario (dict): The scenario to load into the simulator.
        """
        raise NotImplementedError

    def register_callback(self, func: Callable):
        """Registers a callback function for the simulator.

        Args:
            func: The function to register.

        Note:
            - This is used to register some function call that should be executed per tick.
            - For simulator that does not have callback mechanism, user could manually call this function per tick.
        """
        pass

    def apply_control(self, target: str, control: dict):
        """Applies a control to the target.

        Args:
            target: The target to apply the control to.
            control: The control to apply.
        """
        pass

    def get_control(self, identifier: str) -> Any:
        """Return the control that applied in the last tick.

        Args:
            identifier: The identifier of the actor to get the control of.

        Returns:
            Any: The control that applied in the last tick. In simulator specific format.
        """
        pass

    def tick(self) -> int:
        """Ticks the simulator.

        Returns:
            int: The current tick of the simulator.
        """
        raise NotImplementedError
